基本的术语

介绍架构之前,现介绍一下我们现在对于 VS Code 生态中的一些称呼,因为不确定大家是怎么叫的,

架构介绍

electron 与 Web 端的架构本质上大同小异的,他们最大的差别是在于两者的运行时环境,一个是由 electron 包含的 chromium 另一个则是传统的浏览器。 下面我以 Web 端作为例子介绍一下整体架构:

底座本身是一个普通的 Web 应用,但是对于每一个扩展而言,每加载一个扩展底座会通过 webworker 初始化一个插件的运行环境。扩展的 pkg 会在一开始就被主进程的插件服务监听到,拿到扩展的值,此时 pkg 中声明的 command menus view 之类的声明会直接被注册到主进程中的 Registry 中。紧接着,当扩展中的声明的激活时间被触发时,并且此时也满足了其他的条件: 例如是 Web 扩展,vscode 最低版本满足的情况下,扩展的入口文件会被加载。

扩展的 iframe 文件: webWorkerExtensionHostIframe.html 用处:

  1. 加载扩展的静态资源,例如 js/css 等。
  2. 通过 web worker 之间的通信,来支持扩展进程与主进程之间的 sdk api 调用

TODO: 双层 iframe 结构。

而如果扩展有自定义 UI 的需求,会在原来 Web Worker 的基础之上,先加载一张 vs/workbench/contrib/webview/browser/pre/index.html iframe(不要问为什么叫 pre,这里我们也不是很清楚,毕竟国内大多数情况下 pre 是作为预发环境的一个代称),在这张 iframe 中,又会加载一张 fake.html 的空 html,此时是不是很奇怪为什么要用这么奇怪的双层 iframe 架构,fake.html 会被扩展中的 Webview html 字符串替换掉,变成真正的 iframe 用来选择自定义 ui 的内容。

双层 iframe 原因:

  1. 扩展与 Webview 是两个独立的进程。所以需要两张 html 分别来承载。
  2. fake.html 会被替换成 webview html 的内容。如果没有 pre/index.html 这一层,在 Webview 中可以通过 parent 直接修改底座中的 window 全局变量。这样就会导致扩展可以修改底座的 DOM,这是不安全的。
  3. 外层的 iframe 是用 service worker 做数据缓存的。 而内层的 fake.html 是用于真正替换 Webview 的内容的。

1. 隔离性

  1. 底座和扩展之间是互相隔离的。
  2. 扩展和扩展之间也是互相隔离的。
  3. 对于 Webview 来说,自然与包含它的扩展来说也是隔离的。

2. 扩展性

VS Code 通过贡献点(英文名叫底座中被称之为 contribution,扩展配置文件中被称之为 contributies),来支持开发者对各式各样的需求,在一个扩展中可以定义的扩展模块有:

  1. 命令
  2. 菜单
  3. 视图
  4. 配置文件

3. 配置化

  1. 底座扩展用 worker 隔离。
    1. 扩展通过 worker 加载,入口文件是扩展的 package.json 以及 package.json 指定的 js entry 文件。
    2. 对于 Web 版 VS Code 而言加载扩展有很多种方式,实现一套应用市场架构,是最接近 vscode.dev 的; 最简单的加载方式是将扩展的 CDN 入口链接填入 product.json 中的 additionBuiltinExtensions 属性中,默认即可加载。
      1. 但是需要注意的是,非 Web 扩展不能直接在 Web 版的 VS Code 中运行,会加载不成功,会有一条 warning 提示当前的扩展非 Web 扩展。
  2. WebView 加载。
    1. 能够加载 Webview 的位置其实只有 Sidebar 和 Panel. 如果需要定义 Webview,需要在扩展的 Package.json 文件中定义 View 和 ViewContainer
    2. VS Code 注册 View 和 ViewContainer,并且根据 ViewContainer 的位置,在面板可见时计算出相对的偏移量。
    3. 再上头盖一张 Webview 绝对定位。
  3. 扩展与底座之间底层是通过 rpc 字符通信的,所以本质上只能传递字符串,对象(序列化、反序列化之后会丢属性)因此所有的 Provider 都是在扩展中继承 VS Code 的类型实现,实例中声明一个函数,返回一个数组、对象等。

进程

有几个主要进程:

  1. 主进程,负责管理其他进程。
  2. 渲染进程。一个或者多个。
  3. 扩展进程。基本上是多个, remoteExtensionHost, webExtensionHost

进程之前通过 rpc 进行通信 (electron 是直接通过 rpc 通信的;开天的 Web 端与后端是通过 WS 通信;),通过返回一个 Proxy 将底层 rpc 进行封装用于远程调用。

electron 以及 sandbox 架构的升级

sandbox 主要指的是 electron 的 sandbox 模式,现在应该已经处于默认开启阶段,但是也可以关闭。

SDK API 的实现

扩展进程通过 RPC 调用主进程的服务实现。 Web 架构不是 RPC, 而是 Worker 的通信机制。

ExtensionHost 服务提供了一个 SDK (一个对象)环境。

通信

  1. electron

如何实现多端构建

  1. 架构分离,氛围 web/electron 端,有各自的构建脚本和入口。
  2. 使用依赖注入的方式实现。

VS Code 中的 worker 使用

两个都有使用到。

  • Web Worker: 用于实现扩展进程,与主进程隔离。
  • Service Worker: 通信? 需要再看一下
    • 要求 https 的第一个原因

VS Code 这样的架构有什么好处?

  1. 安全。隔离性。
    1. 底座与插件隔离:确保扩展(Worker 访问不到底座的 DOM,也无法访问底座的数据,只能通过 RPC 通信)
    2. 但是 Web 版的架构与 electron 的架构有些差别,electron 端通过 rpc 进行通信,并且在渲染进程中原本可以直接访问 Node 进程中的代码,这是不太安全的。于是 VS Code 花了 2 年时间将 electron 的架构改造成了现在的架构,开启 electron 端的 sandbox 架构:渲染进程只能通过 rpc 访问 Node 的代码(通过暴露的函数,而非直接通过 require fs 等模块)。
    3. 插件隔离:某一个扩展挂掉不会影响其他扩展,也不会影响底座的正常运行。
  2. UI 统一。可以自定义的部分,例如主题、icon 等,扩展几乎没有权限自定义 UI。

架构设计?

  1. 为什么不能使用 react 组件(可以作为一个难点)
  2. 如何将 React 融合进 VS Code 中
    1. VS Code Web 的部署架构

Web 与 Worker 之间的通信方式?

性能优化

  1. 最大的性能问题是 Webview. 简单的页面,通过 Webpack 插件将 js/css 都内联进 html 中。但是对复杂的页面,还是需要通过 CDN 插入。

安全

  1. 监控:监控的指标计算

业务如何接入?

  1. 应用同一份底座,通过指定不同的 product.json 和 web-configraution.json 指定加载不同的 additionBuiltinExtension 初始化不同默认的扩展。

Command 有没有规范?

国际化?

如何设计接口? 新增 api

electron 架构

chromium + Node.js + Native API = electron

  • 使用 Web 技术来编写 UI,用 chrome 浏览器内核来运行
  • 使用 NodeJS 来操作文件系统和发起网络请求
  • 使用 NodeJS C++ Addon 去调用操作系统的 native API

应用架构

  • 1 个主进程:一个 Electron App 只会启动一个主进程,它会运行 package.json 的 main 字段指定的脚本
  • N 个渲染进程:主进程代码可以调用 Chromium API 创建任意多个 web 页面,而 Chromium 本身是多进程架构,每个 web 页面都运行在属于它自己的渲染进程中

进程间通讯:

  • Render 进程之间的通讯本质上和多个 Web 页面之间通讯没有差别,可以使用各种浏览器能力如 localStorage,本质上并不能够直接通信。
  • Render 进程与 Main 进程之间也可以通过 API 互相通讯 (ipcRenderer/ipcMain)

VSCode 技术架构

多进程架构

  • 主进程:VSCode 的入口进程,负责一些类似窗口管理、进程间通信、自动更新等全局任务
  • 渲染进程:负责一个 Web 页面的渲染,这里会存在多个。
  • 插件宿主进程:每个插件的代码都会运行在一个独属于自己的 NodeJS 环境的宿主进程中,插件不允许访问 UI
  • Debug 进程:Debugger 相比普通插件做了特殊化
  • Search 进程:搜索是一类计算密集型的任务,单开进程保证软件整体体验与性能
Last Updated:
Contributors: yiliang114